package qq2564874169.tuaotuao.fx.orm.mybatis;

import org.apache.ibatis.builder.xml.XMLMapperBuilder;
import org.apache.ibatis.logging.stdout.StdOutImpl;
import org.apache.ibatis.mapping.Environment;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.apache.ibatis.session.TransactionIsolationLevel;
import org.apache.ibatis.transaction.TransactionFactory;
import org.apache.ibatis.transaction.jdbc.JdbcTransactionFactory;
import qq2564874169.tuaotuao.fx.FxAutoCloseable;
import qq2564874169.tuaotuao.fx.FxStream;
import qq2564874169.tuaotuao.fx.utils.ResUtil;

import javax.sql.DataSource;
import java.io.IOException;
import java.io.InputStream;
import java.sql.Connection;
import java.util.List;
import java.util.Map;
import java.util.UUID;

public class MybatisContext {
    private final static String SQLKEY = UUID.randomUUID().toString();
    private TransactionFactory transactionFactory;
    private Environment environment;
    private MyConfiguration configuration;
    private SqlSessionFactory sqlSessionFactory;
    private String id;
    private DataSource dataSource;
    private PageInterceptor pageInterceptor;
    private MultipleResultInterceptor multipleResultInterceptor;
    private UseCursorInterceptor useCursorInterceptor;
    private SetTimeoutInterceptor setTimeoutInterceptor;
    private ResultTypeInterceptor resultTypeInterceptor;
    private boolean fixMysqlCursorProp = false;

    public MybatisContext(String id, DataSource dataSource) {
        this.id = id;
        this.dataSource = dataSource;
        this.setTimeoutInterceptor = new SetTimeoutInterceptor();
    }

    public void setFixMysqlCursorProp(boolean enable) {
        fixMysqlCursorProp = enable;
        if (useCursorInterceptor != null) {
            useCursorInterceptor.fixMysqlCursorProp = fixMysqlCursorProp;
        }
    }

    protected PageInterceptor createPageInterceptor() {
        return new PageInterceptor();
    }

    protected MultipleResultInterceptor createMultipleResultInterceptor() {
        return new MultipleResultInterceptor();
    }

    protected UseCursorInterceptor createUseCursorInterceptor() {
        return new UseCursorInterceptor();
    }

    protected ResultTypeInterceptor createResultTypeInterceptor() {
        return new ResultTypeInterceptor();
    }

    protected TransactionFactory createTransactionFactory() {
        return new JdbcTransactionFactory();
    }

    protected Environment createEnvironment(TransactionFactory transactionFactory, DataSource dataSource) {
        return new Environment(id, transactionFactory, dataSource);
    }

    protected boolean isShowSql() {
        return false;
    }

    protected MyConfiguration createConfiguration(DataSource dataSource) {
        transactionFactory = createTransactionFactory();
        environment = createEnvironment(transactionFactory, dataSource);
        configuration = new MyConfiguration(environment);
        if (isShowSql()) {
            configuration.setLogImpl(StdOutImpl.class);
        }
        configuration.setDefaultEnumTypeHandler(EnumIdFieldTypeHandler.class);
        pageInterceptor = createPageInterceptor();
        configuration.addInterceptor(pageInterceptor);
        multipleResultInterceptor = createMultipleResultInterceptor();
        configuration.addInterceptor(multipleResultInterceptor);
        useCursorInterceptor = createUseCursorInterceptor();
        useCursorInterceptor.fixMysqlCursorProp = fixMysqlCursorProp;
        configuration.addInterceptor(useCursorInterceptor);
        resultTypeInterceptor = createResultTypeInterceptor();
        configuration.addInterceptor(resultTypeInterceptor);
        configuration.addMapper(SqlProvider.class);
        return configuration;
    }

    public SqlSession getNewSession(boolean autoCommit) {
        return getSqlSessionFactory().openSession(autoCommit);
    }

    public SqlSession getNewSession(TransactionIsolationLevel level) {
        return getSqlSessionFactory().openSession(level);
    }

    public FxAutoCloseable multipleSpace() {
        return multipleResultInterceptor.resultSetSpace();
    }

    public FxAutoCloseable pageSpace(SqlPager pager) {
        return pageInterceptor.pageSpace(pager);
    }

    public FxAutoCloseable useCursor(Connection connection, int fetchSize) {
        return useCursorInterceptor.set(connection, fetchSize);
    }

    public FxAutoCloseable setResultType(Class<?> type) {
        return resultTypeInterceptor.setResultType(type);
    }

    public SqlSessionFactory getSqlSessionFactory() {
        if (sqlSessionFactory == null) {
            configuration = createConfiguration(dataSource);
            sqlSessionFactory = new SqlSessionFactoryBuilder().build(configuration);
        }
        return sqlSessionFactory;
    }

    public void loadXmlMapper(List<String> files) {
        loadXmlMapper(files, null);
    }

    public synchronized void loadXmlMapper(List<String> files, Class<?> loader) {
        if (files == null) {
            return;
        }
        MyConfiguration configuration = (MyConfiguration) getSqlSessionFactory().getConfiguration();
        for (String file : files) {
            configuration.addBefore(file);
            try (InputStream is = ResUtil.getStream(file, loader)) {
                if (is != null) {
                    String res = "MXML-" + UUID.randomUUID().toString().replace("-", "");
                    XMLMapperBuilder mapBuilder = new XMLMapperBuilder(is, configuration, res, configuration.getSqlFragments());
                    mapBuilder.parse();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
            configuration.addAfter();
        }
    }

    public List<String> getStmtNames() {
        return FxStream.create(configuration.getMappedStatementNames()).toList();
    }

    public String getXmlSql(String msid) {
        return getXmlSql(msid, null);
    }

    public String getXmlSql(String msid, Object param) {
        MappedStatement ms = configuration.getMappedStatement(msid);
        if (ms.getResource().startsWith("MXML-")) {
            return ms.getBoundSql(param).getSql();
        }
        return "";
    }

    static void setSql(Map<String, Object> map, String sql) {
        map.put(SQLKEY, sql);
    }

    static String getSql(Map<String, Object> map) {
        return map.get(SQLKEY).toString();
    }

    void setTimeout(int second) {
        if (setTimeoutInterceptor != null) {
            setTimeoutInterceptor.setTimeout(second);
        }
    }
}
